import 'dart:math' as math;
import 'dart:ui' show Size;

import 'package:flutter/material.dart';

import '../cut_canvas.dart';
import 'adaptive_scale.dart';

/// Given a [CanvasScaleState], returns a scale value considering scaleBoundaries.
double getScaleForScaleState(
  CanvasScaleState scaleState,
  ScaleBoundaries scaleBoundaries,
) {
  switch (scaleState) {
    case CanvasScaleState.initial:
    case CanvasScaleState.zoomedIn:
    case CanvasScaleState.zoomedOut:
      return _clampSize(scaleBoundaries.initialScale, scaleBoundaries);
    case CanvasScaleState.covering:
      return _clampSize(
          _scaleForCovering(
              scaleBoundaries.outerSize, scaleBoundaries.childSize),
          scaleBoundaries);
    case CanvasScaleState.originalSize:
      double doubleTapScale = scaleBoundaries.maxScale * (2 / 3);
      return _clampSize(doubleTapScale, scaleBoundaries);
    default:
      return null;
  }
}

/// Internal class to wraps custom scale boundaries (min, max and initial)
/// Also, stores values regarding the two sizes: the container and teh child.
class ScaleBoundaries {
  ScaleBoundaries(
    this._minScale,
    this._maxScale,
    this._initialScale,
    this.outerSize,
    this.childSize,
  );

  final dynamic _minScale;
  final dynamic _maxScale;
  final dynamic _initialScale;
  Size outerSize;
  final Size childSize;

  double get minScale {
    assert(_minScale is double || _minScale is AdaptiveScale);
    if (_minScale is AdaptiveScale) {
      if (_minScale == AdaptiveScale.contained) {
        return _scaleForContained(outerSize, childSize) * _minScale.multiplier;
      }
      if (_minScale == AdaptiveScale.covered) {
        return _scaleForCovering(outerSize, childSize) * _minScale.multiplier;
      }
    }
    assert(_minScale >= 0.0);
    return _minScale;
  }

  double get maxScale {
    assert(_maxScale is double || _maxScale is AdaptiveScale);
    if (_maxScale == AdaptiveScale.contained) {
      return (_scaleForContained(outerSize, childSize) *
              (_maxScale as AdaptiveScale) // ignore: avoid_as
                  .multiplier)
          .clamp(minScale, double.infinity);
    }
    if (_maxScale == AdaptiveScale.covered) {
      return (_scaleForCovering(outerSize, childSize) *
              (_maxScale as AdaptiveScale) // ignore: avoid_as
                  .multiplier)
          .clamp(minScale, double.infinity);
    }
    return _maxScale.clamp(minScale, double.infinity);
  }

  double get initialScale {
    assert(_initialScale is double || _initialScale is AdaptiveScale);
    if (_initialScale == AdaptiveScale.contained) {
      return _scaleForContained(outerSize, childSize) *
          (_initialScale as AdaptiveScale) // ignore: avoid_as
              .multiplier;
    }
    if (_initialScale == AdaptiveScale.covered) {
      return _scaleForCovering(outerSize, childSize) *
          (_initialScale as AdaptiveScale) // ignore: avoid_as
              .multiplier;
    }
    return _initialScale.clamp(minScale, maxScale);
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ScaleBoundaries &&
          runtimeType == other.runtimeType &&
          _minScale == other._minScale &&
          _maxScale == other._maxScale &&
          _initialScale == other._initialScale &&
          outerSize == other.outerSize &&
          childSize == other.childSize;

  @override
  int get hashCode =>
      _minScale.hashCode ^
      _maxScale.hashCode ^
      _initialScale.hashCode ^
      outerSize.hashCode ^
      childSize.hashCode;
}

double _scaleForContained(Size size, Size childSize) {
  final double imageWidth = childSize.width;
  final double imageHeight = childSize.height;

  final double screenWidth = size.width;
  final double screenHeight = size.height;

  double contained =
      math.min(screenWidth / imageWidth, screenHeight / imageHeight);
  return contained;
}

double _scaleForCovering(Size size, Size childSize) {
  final double imageWidth = childSize.width;
  final double imageHeight = childSize.height;

  final double screenWidth = size.width;
  final double screenHeight = size.height;

  return math.max(screenWidth / imageWidth, screenHeight / imageHeight);
}

double _clampSize(double size, ScaleBoundaries scaleBoundaries) {
  return size.clamp(scaleBoundaries.minScale, scaleBoundaries.maxScale);
}

/// Simple class to store a min and a max value
class CornersRange {
  const CornersRange(this.min, this.max);

  final double min;
  final double max;

  @override
  String toString() {
    return 'CornersRange{min: $min, max: $max}';
  }
}

//double getRotateResizeScale(Size outSize, double rotation) {
//  double angle = (rotation ?? 0) / 2 / math.pi * 360;
//
//  double tmpAngle = 90 - (90 - angle.abs() % 180).abs();
//  double tmpRotation = tmpAngle / 360 * 2 * math.pi;
//
//  double rotateWidth = math.cos(math.atan2(
//              math.max(outSize.width, outSize.height),
//              math.min(outSize.width, outSize.height)) -
//          tmpRotation) *
//      math.sqrt(outSize.width * outSize.width / 4 +
//          outSize.height * outSize.height / 4);
//  return (rotateWidth / (math.min(outSize.width, outSize.height) / 2));
//}

double getRotateResizeScale(double aspectRatio, double rotation) {
  double angle = (rotation ?? 0) / 2 / math.pi * 360;

  double tmpAngle = 90 - (90 - angle.abs() % 180).abs();
  double tmpRotation = tmpAngle / 360 * 2 * math.pi;

  double rotateWidth = math.cos(
          math.atan2(math.max(aspectRatio, 1), math.min(aspectRatio, 1)) -
              tmpRotation) *
      math.sqrt(aspectRatio * aspectRatio / 4 + 1 * 1 / 4);
  return (rotateWidth / (math.min(aspectRatio, 1) / 2));
}

Offset getTensionOffset(Offset overOffset, Offset boundOffset) {
  return Offset(
    _getTensionValue(overOffset.dx, boundOffset.dx),
    _getTensionValue(overOffset.dy, boundOffset.dy),
  );
}

const double baseDistance = 360;
const double factor = 0.4;

double _getTensionValue(double overValue, double boundValue) {
  if (overValue > boundValue) {
    // 归一化
    double overPast = (overValue - boundValue) / baseDistance;
    double absDelta = 2 * overPast - overPast * overPast;
    return boundValue + absDelta * baseDistance * factor;
  } else {
    // 归一化
    double overPast = (boundValue - overValue) / baseDistance;
    double absDelta = 2 * overPast - overPast * overPast;
    return boundValue - absDelta * baseDistance * factor;
  }
}
